package glmodel;

import java.nio.*;
import org.lwjgl.opengl.*;

/**
 * Wraps properties for a material. To use: create a new material, set the
 * material properties (setDiffuse(), setAmbient(), setSpecular(),
 * setShininess()), call material.apply() in your render function to set the
 * OpenGL current material properties.
 */
public class GLMaterial
{
	// A sampling of color values
	public static final float	colorNone[]				= {0f, 0f, 0f, 1f};							// no color = black
	public static final float	colorDefaultDiffuse[]	= {.8f, .8f, .8f, 1f};							// OpenGL default diffuse color
	public static final float	colorDefaultAmbient[]	= {.2f, .2f, .2f, 1f};							// OpenGL default ambient color
	public static final float	minShine				= 0.0f;
	public static final float	maxShine				= 127.0f;

	// Default material values
	private static FloatBuffer	defaultDiffuse			= allocFloats(colorDefaultDiffuse);
	private static FloatBuffer	defaultAmbient			= allocFloats(colorDefaultAmbient);
	private static FloatBuffer	defaultSpecular			= allocFloats(colorNone);
	private static FloatBuffer	defaultEmission			= allocFloats(colorNone);
	private static FloatBuffer	defaultShine			= allocFloats(new float[]{minShine, 0, 0, 0});	// LWJGL requires four values, so include three extra zeroes

	// The color values for this material
	public FloatBuffer			diffuse;																// color of the lit surface
	public FloatBuffer			ambient;																// color of the shadowed surface
	public FloatBuffer			specular;																// reflection color (typically this is a shade of gray)
	public FloatBuffer			emission;																// glow color
	public FloatBuffer			shininess;																// size of the reflection highlight

	// hold name and texture values for this material
	public String				mtlname					= "noname";									// name of this material in the .mtl and .obj files
	public String				textureFile				= null;										// texture filename (null if no texture)
	public int					textureHandle;															// opengl handle to the texture (0 if no texture)

	public GLMaterial()
	{
		setDefaults();
	}

	/**
	 * Set the material to OpenGL's default values (gray, with no reflection and
	 * no glow)
	 */
	public void setDefaults()
	{
		setDiffuse(colorDefaultDiffuse);
		setAmbient(colorDefaultAmbient);
		setSpecular(colorNone);
		setEmission(colorNone);
		setShininess(minShine);
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Functions to set the material properties * * * * * * * * * * * * * * * *
	 * * * * * * * * * * * * * * * *
	 */

	/**
	 * Set the diffuse material color. This is the color of the material where
	 * it is directly lit.
	 */
	public void setDiffuse(float[] color)
	{
		diffuse = allocFloats(color);
	}

	/**
	 * Set the ambient material color. This is the color of the material where
	 * it is lit by indirect light (light scattered off the environment). Ie.
	 * the shadowed side of an object.
	 */
	public void setAmbient(float[] color)
	{
		ambient = allocFloats(color);
	}

	/**
	 * Set the specular material color. This controls how much light is
	 * reflected off a glossy surface. This color value describes the brightness
	 * of the reflection and is typically a shade of gray. Pure black means that
	 * no light is reflected (ie. a very rough matte surface). Pure white means
	 * that the surface is highly reflective, see also: setShininess()
	 */
	public void setSpecular(float[] color)
	{
		specular = allocFloats(color);
	}

	/**
	 * Set the emission material color. This controls the "glow" of the
	 * material, and can be used to make a material that seems to be lit from
	 * inside.
	 */
	public void setEmission(float[] color)
	{
		emission = allocFloats(color);
	}

	/**
	 * Set size of the reflection highlight. Must also set the specular color
	 * for shininess to have any effect: setSpecular(GLMaterial.colorWhite);
	 * 
	 * @param howShiny
	 *            How sharp reflection is: 0 - 127 (127=very sharp pinpoint)
	 */
	public void setShininess(float howShiny)
	{
		if (howShiny >= minShine && howShiny <= maxShine)
		{
			float[] tmp = {howShiny, 0, 0, 0};
			shininess = allocFloats(tmp);
		}
	}

	/**
	 * Call glMaterial() to activate these material properties in the OpenGL
	 * environment. These properties will stay in effect until you change them
	 * or disable lighting.
	 */
	public void apply()
	{
		// GL_FRONT: affect only front facing triangles
		GL11.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, diffuse);
		GL11.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, ambient);
		GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, specular);
		GL11.glMaterial(GL11.GL_FRONT, GL11.GL_EMISSION, emission);
		GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, shininess);
	}

	/**
	 * Sets the material color to approximate a "real" surface color. Use the
	 * same color for diffuse and ambient. To create a shadowed effect you
	 * should lower the ambient value for the light sources and lower the
	 * overall ambient light.
	 */
	public void setColor(float[] color)
	{
		setDiffuse(color); // surface directly lit
		setAmbient(color); // surface in shadow
	}

	/**
	 * Make material appear to emit light
	 */
	public void setGlowColor(float[] color)
	{
		emission = allocFloats(color);
	}

	/**
	 * alpha value is set in the diffuse material color. Other material colors
	 * (ambient, specular) are not affected by alpha value.
	 * 
	 * @param alphaVal
	 *            0 - 1
	 */
	public void setAlpha(float alphaVal)
	{
		diffuse.put(3, alphaVal);
	}

	/**
	 * alpha value is stored in the diffuse material color alpha. Other material
	 * colors (ambient, specular) are not affected by alpha value.
	 * 
	 * @return alphaVal 0 - 1
	 */
	public float getAlpha()
	{
		return diffuse.get(3);
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * functions to add a texture to this material * * * * * * * * * * * * * * *
	 * * * * * * * * * * * * * * * * *
	 */

	/**
	 * Store a texture filename with the material.
	 */
	public void setTextureFile(String s)
	{
		textureFile = s;
	}

	/**
	 * Assign a texture handle to this material.
	 * 
	 * @param txtrHandle
	 */
	public void setTexture(int txtrHandle)
	{
		textureHandle = txtrHandle;
	}

	public String getTextureFile()
	{
		return textureFile;
	}

	public int getTexture()
	{
		return textureHandle;
	}

	/**
	 * set the material name. This is the name assigned to this texture in the
	 * .mtl file. It is NOT a filename.
	 * 
	 * @param s
	 */
	public void setName(String s)
	{
		mtlname = s;
	}

	public String getName()
	{
		return mtlname;
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Native IO buffer functions * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * *
	 */

	public static final int	SIZE_FLOAT	= 4;	// four bytes in a float

	public static FloatBuffer allocFloats(float[] floatarray)
	{
		FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();
		fb.put(floatarray).flip();
		return fb;
	}

}